#include <net/genetlink.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/list.h>
#include <linux/slab.h>
#include "../common.h"

static LIST_HEAD(head_test);

struct test_node {
    uint32_t ip;
    uint16_t port;
    struct list_head list;
};

static struct nla_policy test_genl_policy[TEST_ATTR_MAX + 1] = {
    [TEST_ATTR_IP]    = { .type = NLA_U32 },
    [TEST_ATTR_PORT]  = { .type = NLA_U16 },
};

static const struct genl_multicast_group test_multicast_group[] = {
    {
        .name = "test",
    }
};

int test_genl_cmd_add(struct sk_buff *skb, struct genl_info *info);
int test_genl_cmd_del(struct sk_buff *skb, struct genl_info *info);

static struct genl_ops test_genl_ops[] = {
    {
        .cmd = TEST_CMD_ADD,
        .policy = test_genl_policy,
        .doit = test_genl_cmd_add,
    },
    {
        .cmd = TEST_CMD_DEL,
        .policy = test_genl_policy,
        .doit = test_genl_cmd_del,
    }
};

/* 定义自己的generic netlink family */
static struct genl_family test_genl_family = {
    .hdrsize = 0, /* 自定义头部长度 */
    .version = TEST_GENL_VERSION,
    .maxattr = TEST_ATTR_MAX,
    .name = "test",  /* id由内核分配的,可以根据name查找到id */
    .module = THIS_MODULE,
    .ops = test_genl_ops,
    .n_ops = ARRAY_SIZE(test_genl_ops),
    .mcgrps = test_multicast_group,
    .n_mcgrps = ARRAY_SIZE(test_multicast_group),
};

static int test_node_add(uint32_t ip, uint16_t port)
{
    struct test_node *entry = kmalloc(sizeof(*entry), GFP_KERNEL);
    if(NULL == entry) {
        printk(KERN_ERR"%s(): kmalloc() failed.\n", __func__);
        return -1;
    }
    entry->ip = ip;
    entry->port = port;
    list_add_tail(&(entry->list), &head_test);
    return 0;
}

static int test_node_del(uint32_t ip, uint16_t port)
{
    struct list_head *pos, *n;
    list_for_each_safe(pos, n, &head_test) {
        struct test_node *entry = list_entry(pos, struct test_node, list);
        if((entry->ip == ip) && (entry->port == port)) {
            list_del(&(entry->list));
            kfree(entry);
            return 0;
        }
    }
    return -1;
}

static struct test_node *test_node_get(uint32_t ip, uint16_t port)
{
    struct list_head *pos;
    list_for_each(pos, &head_test) {
        struct test_node *entry = list_entry(pos, struct test_node, list);
        if((entry->ip == ip) && (entry->port == port)) {
            return entry;
        }
    }
    return NULL;
}

int test_genl_cmd_add(struct sk_buff *skb, struct genl_info *info)
{
    struct sk_buff *skb_rpl = NULL;
    int ret = 0;
    void *genlmsghdr = NULL;
    uint32_t ip = 0;
    uint16_t port = 0;

    if (info == NULL) {
        return -1;
    }

    if(info->attrs[TEST_ATTR_IP] && info->attrs[TEST_ATTR_PORT]) {
        ip = nla_get_u32(info->attrs[TEST_ATTR_IP]);
        port = nla_get_u16(info->attrs[TEST_ATTR_PORT]);
        printk(KERN_EMERG"%s() %pI4:%u\n", __func__, &ip, ntohs(port));
        struct test_node *tmp = test_node_get(ip, port);
        if(!tmp) {
            ret = test_node_add(ip, port);
        }
        else {
            ret = -2;
        }
    }
    else {
        printk(KERN_EMERG"%s() no info->attrs %i or %i\n", __func__, TEST_ATTR_IP, TEST_ATTR_PORT);
        ret = -1;
    }

    if(ret < 0) {
        goto out;
    }

    /* 新建一个netlink的skb */
    skb_rpl = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
    if (skb_rpl == NULL) {
        return -ENOMEM;
    }

    /* 向netlink消息中添加generic netink报文头 */
    genlmsghdr = genlmsg_put(skb_rpl, info->snd_portid,
            info->snd_seq, &test_genl_family, NLM_F_ACK, TEST_CMD_ADD);
    if(NULL == genlmsghdr) {
        ret = -ENOMEM;
        goto out;
    }

    /* 向netlink中添加属性 */
    ret = nla_put_u32(skb_rpl, TEST_ATTR_IP, ip);
    if(ret != 0) {
        goto out;
    }

    ret = nla_put_u16(skb_rpl, TEST_ATTR_PORT, port);
    if(ret != 0) {
        goto out;
    }

    /* 完成回复消息的封装 */
    genlmsg_end(skb_rpl, genlmsghdr);

    /* 发送回复包
     * 如果发送回复的单播包要加portid，那么添加genlmsghdr_put()的时候
     * 要不要设置portid???
     * 这个genlmsg_unicast()发送完后在哪里free skb, 还是说不用free
     */
    return genlmsg_unicast(genl_info_net(info), skb_rpl, info->snd_portid);

out:
    nlmsg_free(skb_rpl);
    return ret;
}

int test_genl_cmd_del(struct sk_buff *skb, struct genl_info *info)
{
    struct sk_buff *skb_rpl = NULL;
    int ret = 0;
    void *genlmsghdr = NULL;
    uint32_t ip = 0;
    uint16_t port = 0;

    if (info == NULL) {
        return -1;
    }

    if(info->attrs[TEST_ATTR_IP] && info->attrs[TEST_ATTR_PORT]) {
        ip = nla_get_u32(info->attrs[TEST_ATTR_IP]);
        port = nla_get_u16(info->attrs[TEST_ATTR_PORT]);
        printk(KERN_EMERG"%s() %pI4:%u\n", __func__, &ip, ntohs(port));
        ret = test_node_del(ip, port);
    }
    else {
        printk(KERN_EMERG"%s() no info->attrs %i or %i\n", __func__, TEST_ATTR_IP, TEST_ATTR_PORT);
        ret = -1;
    }

    if(ret < 0) {
        goto out;
    }

    /* 新建一个netlink的skb */
    skb_rpl = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
    if (skb_rpl == NULL) {
        return -ENOMEM;
    }

    /* 向netlink消息中添加generic netink报文头 */
    genlmsghdr = genlmsg_put(skb_rpl, info->snd_portid,
            info->snd_seq, &test_genl_family, NLM_F_ACK, TEST_CMD_DEL);
    if(NULL == genlmsghdr) {
        ret = -ENOMEM;
        goto out;
    }

    /* 向netlink中添加属性 */
    ret = nla_put_u32(skb_rpl, TEST_ATTR_IP, ip);
    if(ret != 0) {
        goto out;
    }

    ret = nla_put_u16(skb_rpl, TEST_ATTR_PORT, port);
    if(ret != 0) {
        goto out;
    }

    /* 完成回复消息的封装 */
    genlmsg_end(skb_rpl, genlmsghdr);

    /* 发送回复包
     * 如果发送回复的单播包要加portid，那么添加genlmsghdr_put()的时候
     * 要不要设置portid???
     * 这个genlmsg_unicast()发送完后在哪里free skb, 还是说不用free
     */
    return genlmsg_unicast(genl_info_net(info), skb_rpl, info->snd_portid);

out:
    nlmsg_free(skb_rpl);
    return ret;
}

static int __init test_genl_init(void)
{
    int ret = genl_register_family(&test_genl_family);
    if(ret != 0) {
        printk(KERN_EMERG"%s(): genl_register_family failed\n", __func__);
    }
    else {
        printk(KERN_EMERG"%s(): genl_register_family ok\n", __func__);
    }
    return ret;
}

static void __exit test_genl_exit(void)
{
    genl_unregister_family(&test_genl_family);
    printk(KERN_EMERG"%s(): genl_unregister_family ok\n", __func__);
}

module_init(test_genl_init);
module_exit(test_genl_exit);
MODULE_LICENSE("GPL");
